
"""

TODO

Z-Fighting

current implementaion looks bad from steep angles, better idea needed

card = NodePath(cardMaker.generate())
node = card.attachNewNode(textNode)
card.setEffect(DecalEffect.make())

DepthOffset?

LineEdit

- cursor should be an image
- cursor can blink (either through an event, or through an updateVisual call)
- cursor should be invislbe in disabled state
- is tab is a valid character in a LineEdit? Rethink selection if yes.
- better character handling, current method cannot recognize umlauts e.g.
- text lines beyond the available space possible?
- clipping planes (works but not complete)?
- scissor works in screen space. no go.

base.buttonThrowers[0].node().setKeystrokeEvent("keystroke") # unfortunately only "down" events, but recognizes umlauts
base.accept("keystroke", todo)

- mouse events should be filtered as well

Label

- not selectable, not clickable, ... -> always disabled?

Horizontal/Vertical ScrollBar

- TODO

CheckBox

- add label

RadioButton

- add label

RadioGroup

- not selectable, ... -> always disabled?
- more a grouping element and not a visual element?

LayoutManager, VerticalLayout, HorizontalLayout, ...?

- TODO

ClickSound/RolloverSound? Overload?

- Global enable/disable?
- TODO

Zoom?

- TODO

DISPLAY_...

should not be visible outside this class. setEnable(True/False) better?

"""

__all__ = [
	"WHATEVER_AUTO",
	"setGlobalNode",
	"globalNode",
	"WhateverWidget",
	"WhateverButton",
	"WhateverLineEdit",
	"WhateverSlider",
	"WhateverCheckBox",
	"WhateverResources",
	"WhateverMingleMangle",
	"WhateverTexture",
	"DISPLAY_NORMAL",
	"DISPLAY_SELECTED",
	"DISPLAY_HOVERED",
	"DISPLAY_PRESSED",
	"DISPLAY_DISABLED",
]

# from pandac.PandaModules import FontPool # can we fetch the default TextFont somehow from here?
from pandac.PandaModules import CardMaker
from pandac.PandaModules import MouseButton
from pandac.PandaModules import NodePath
from pandac.PandaModules import PNMImage
from pandac.PandaModules import TextFont
from pandac.PandaModules import TextNode
from pandac.PandaModules import TextProperties
from pandac.PandaModules import Texture
from pandac.PandaModules import DecalEffect
from pandac.PandaModules import TransparencyAttrib
from pandac.PandaModules import Plane
from pandac.PandaModules import PlaneNode

WHATEVER_AUTO = 0.0

globalNode = aspect2d

def setGlobalNode(node):
	global globalNode
	globalNode = node

def calcFramePositions(x, y, width, height, texture):
	x, y, width, height = float(x), float(y), float(width), float(height)

	if (width == WHATEVER_AUTO) and (height == WHATEVER_AUTO):
		width = 1.0
		height = 1.0
	elif width == 0.0:
		width = height * texture.getXSize() / texture.getYSize()
	elif height == 0.0:
		height = width * texture.getYSize() / texture.getXSize()

	left = x
	right = left + width
	bottom = y
	top = bottom + height

	return left, right, bottom, top

def addCard(left, right, bottom, top, z):
	card = CardMaker("Card")
	card.setFrame(left, right, bottom, top)
	node = globalNode.attachNewNode(card.generate())
	node.setTransparency(TransparencyAttrib.MAlpha)
	node.setPos(0.0, z, 0.0)

	return node

DISPLAY_NORMAL, DISPLAY_SELECTED, DISPLAY_HOVERED, DISPLAY_PRESSED, DISPLAY_DISABLED = range(5)
CHECK_CHECKED, CHECK_UNCHECKED = range(2)

Z0, Z1 = -0.02, -0.04

class WhateverWidget(object):
	def mouseMoveEvent(self, x, y, pressed):
		pass

	def mousePressEvent(self):
		pass

	def mouseReleaseEvent(self):
		pass

	def keyPressEvent(self, key):
		pass

	def keyReleaseEvent(self, key):
		pass

class WhateverButton(WhateverWidget):
	def __init__(self, resources, x, y, width, height):
		self.resources = resources

		self.left, self.right, self.bottom, self.top = calcFramePositions(x, y, width, height, self.resources.buttonNormalTexture)
		self.displayNode = addCard(self.left, self.right, self.bottom, self.top, Z0)
		self.setDisplayState(DISPLAY_NORMAL)

		self.text = TextNode("Text")
		if self.resources.font != None:
			self.text.setFont(self.resources.font)
		self.text.setText("?")
		self.text.setAlign(TextProperties.ACenter)
		#self.text.setFrameColor(1.0, 0.0, 1.0, 1.0)
		#self.text.setFrameAsMargin(0.001, 0.001, 0.001, 0.001)
		self.textNode = globalNode.attachNewNode(self.text)
		scale = (self.top - self.bottom) / 2.0
		self.textNode.setScale(scale)
		self.textNode.setColor(0.0, 0.0, 0.0)
		textX = x + (self.right - self.left) / 2.0
		textY = y + (self.top - self.bottom) / 2.0 - self.text.getLineHeight() * scale / 4.0 # empirical
		self.textNode.setPos(textX, Z1, textY)

	def setText(self, caption):
		self.text.setText(caption)

	def isPointInside(self, x, y):
		return (x >= self.left) and (x <= self.right) and (y >= self.bottom) and (y <= self.top)

	def setDisplayState(self, state):
		if state == DISPLAY_NORMAL:
			self.displayNode.setTexture(self.resources.buttonNormalTexture)
		if state == DISPLAY_SELECTED:
			self.displayNode.setTexture(self.resources.buttonSelectedTexture)
		if state == DISPLAY_HOVERED:
			self.displayNode.setTexture(self.resources.buttonHoveredTexture)
		if state == DISPLAY_PRESSED:
			self.displayNode.setTexture(self.resources.buttonPressedTexture)
		if state == DISPLAY_DISABLED:
			self.displayNode.setTexture(self.resources.buttonDisabledTexture)
		self.displayState = state

	def getDisplayState(self):
		return self.displayState

	def mouseReleaseEvent(self):
		super(WhateverButton, self).mouseReleaseEvent()
		self.resources.buttonClickSound.play()

class WhateverLineEdit(WhateverWidget):
	def __init__(self, resources, x, y, width, height):
		self.resources = resources

		self.left, self.right, self.bottom, self.top = calcFramePositions(x, y, width, height, self.resources.lineeditNormalTexture)
		self.displayNode = addCard(self.left, self.right, self.bottom, self.top, Z0)
		self.setDisplayState(DISPLAY_NORMAL)

		self.text = TextNode("Text")
		if self.resources.font != None:
			self.text.setFont(self.resources.font)
		self.text.setAlign(TextProperties.ALeft)
		self.textNode = globalNode.attachNewNode(self.text)
		scale = (self.top - self.bottom) / 2.0
		self.textNode.setScale(scale)
		self.textNode.setColor(0.0, 0.0, 0.0)
		xOffset = (self.text.getLineHeight() * scale / 4.0) # emprical
		yOffset = self.text.getLineHeight() * scale / 4.0 # empirical
		textX = x + xOffset
		textY = y + (self.top - self.bottom) / 2.0 - yOffset
		self.textNode.setPos(textX, Z1, textY)

		clip = self.displayNode.attachNewNode(PlaneNode('clip'))
		clip.node().setPlane(Plane(-1.0, 0.0, 0.0, self.right - self.left - 2.0 * xOffset))
		self.textNode.setClipPlane(clip)

		self.setText("?")

	def setText(self, content):
		self.maxLength = 100
		self.content = content[:self.maxLength]
		self.text.setText(content + "|")

	def isPointInside(self, x, y):
		return (x >= self.left) and (x <= self.right) and (y >= self.bottom) and (y <= self.top)

	def setDisplayState(self, state):
		if state == DISPLAY_NORMAL:
			self.displayNode.setTexture(self.resources.lineeditNormalTexture)
		if state == DISPLAY_SELECTED:
			self.displayNode.setTexture(self.resources.lineeditSelectedTexture)
		if state == DISPLAY_HOVERED:
			self.displayNode.setTexture(self.resources.lineeditHoveredTexture)
		if state == DISPLAY_PRESSED:
			self.displayNode.setTexture(self.resources.lineeditPressedTexture)
		if state == DISPLAY_DISABLED:
			self.displayNode.setTexture(self.resources.lineeditDisabledTexture)
		self.displayState = state

	def getDisplayState(self):
		return self.displayState

	def keyPressEvent(self, key):
		super(WhateverLineEdit, self).keyPressEvent(key)
		if len(key) == 1:
			s = self.content + key
		elif key == "space":
			s = self.content + " "
		elif key.startswith("shift-"):
			s = self.content + key[-1].upper()
		elif key == "backspace":
			s = self.content[:-1]
		else:
			s = self.content
		self.setText(s)

class WhateverSlider(WhateverWidget):
	def __init__(self, resources, x, y, width, height):
		self.resources = resources

		self.left, self.right, self.bottom, self.top = calcFramePositions(x, y, width, height, self.resources.sliderNormalTexture)
		self.displayNode = addCard(self.left, self.right, self.bottom, self.top, Z0)
		self.setDisplayState(DISPLAY_NORMAL)

		sliderWidth = self.resources.sliderSliderTexture.getXSize() * (self.right - self.left) / self.resources.sliderNormalTexture.getXSize()
		sliderHeight = self.resources.sliderSliderTexture.getYSize() * (self.top - self.bottom) / self.resources.sliderNormalTexture.getYSize()
		self.sliderLeft = 0.0
		self.sliderRight = (self.right - self.left) - sliderWidth
		self.sliderNode = addCard(x, x + sliderWidth, y, y + sliderHeight, Z1)
		self.sliderNode.setTexture(self.resources.sliderSliderTexture)

	def isPointInside(self, x, y):
		return (x >= self.left) and (x <= self.right) and (y >= self.bottom) and (y <= self.top)

	def setDisplayState(self, state):
		if state == DISPLAY_NORMAL:
			self.displayNode.setTexture(self.resources.sliderNormalTexture)
		elif state == DISPLAY_SELECTED:
			self.displayNode.setTexture(self.resources.sliderSelectedTexture)
		elif state == DISPLAY_HOVERED:
			self.displayNode.setTexture(self.resources.sliderHoveredTexture)
		elif state == DISPLAY_PRESSED:
			self.displayNode.setTexture(self.resources.sliderPressedTexture)
		elif state == DISPLAY_DISABLED:
			self.displayNode.setTexture(self.resources.sliderDisabledTexture)
		self.displayState = state

	def getDisplayState(self):
		return self.displayState

	def mouseMoveEvent(self, x, y, pressed):
		super(WhateverSlider, self).mouseMoveEvent(x, y, pressed)
		if pressed:
			localX = x - self.left
			localX = max(localX, self.sliderLeft)
			localX = min(localX, self.sliderRight)
			self.sliderNode.setX(localX)

class WhateverCheckBox(WhateverWidget):
	def __init__(self, resources, x, y, width, height):
		self.resources = resources

		self.left, self.right, self.bottom, self.top = calcFramePositions(x, y, width, height, self.resources.checkNormalTexture)
		self.displayNode = addCard(self.left, self.right, self.bottom, self.top, Z0)
		self.setDisplayState(DISPLAY_NORMAL)

		self.checkNode = addCard(self.left, self.right, self.bottom, self.top, Z1)
		self.setCheckState(CHECK_UNCHECKED)

	def isPointInside(self, x, y):
		return (x >= self.left) and (x <= self.right) and (y >= self.bottom) and (y <= self.top)

	def setDisplayState(self, state):
		if state == DISPLAY_NORMAL:
			self.displayNode.setTexture(self.resources.checkNormalTexture)
		elif state == DISPLAY_SELECTED:
			self.displayNode.setTexture(self.resources.checkSelectedTexture)
		elif state == DISPLAY_HOVERED:
			self.displayNode.setTexture(self.resources.checkHoveredTexture)
		elif state == DISPLAY_PRESSED:
			self.displayNode.setTexture(self.resources.checkPressedTexture)
		elif state == DISPLAY_DISABLED:
			self.displayNode.setTexture(self.resources.checkDisabledTexture)
		self.displayState = state

	def getDisplayState(self):
		return self.displayState

	def setCheckState(self, state):
		if state == CHECK_CHECKED:
			self.checkNode.setTexture(self.resources.checkCheckedTexture)
		elif state == CHECK_UNCHECKED:
			self.checkNode.setTexture(self.resources.checkUncheckedTexture)
		self.checkState = state

	def toggleCheck(self):
		if self.checkState == CHECK_CHECKED:
			self.setCheckState(CHECK_UNCHECKED)
		else:
			self.setCheckState(CHECK_CHECKED)

	def mouseReleaseEvent(self):
		super(WhateverCheckBox, self).mouseReleaseEvent()
		self.toggleCheck()

	def keyPressEvent(self, key):
		super(WhateverCheckBox, self).keyPressEvent(key)
		if key == "space":
			self.toggleCheck()

def textureGrayscale(texture):
	image = PNMImage()
	texture.store(image)
	image.makeGrayscale()
	texture = Texture("-")
	texture.load(image)
	return texture

def textureColorScale(texture, scale):
	width, height = texture.getXSize(), texture.getYSize()
	image = PNMImage()
	texture.store(image)
	for y in range(height):
		for x in range(width):
			color = image.getXel(x, y)
			image.setXel(x, y, color[0] * scale, color[1] * scale, color[2] * scale)
	texture = Texture("-")
	texture.load(image)
	return texture

class WhateverTexture(WhateverWidget):
	def __init__(self, resources, x, y, width, height, texture):
		self.normalTexture = texture
		self.lighterTexture = textureColorScale(texture, 1.3)
		self.grayscaleTexture = textureGrayscale(texture)

		self.left, self.right, self.bottom, self.top = calcFramePositions(x, y, width, height, self.normalTexture)
		self.displayNode = addCard(self.left, self.right, self.bottom, self.top, Z0)
		self.setDisplayState(DISPLAY_NORMAL)

	def isPointInside(self, x, y):
		return (x >= self.left) and (x <= self.right) and (y >= self.bottom) and (y <= self.top)

	def setDisplayState(self, state):
		if state == DISPLAY_NORMAL:
			self.displayNode.setTexture(self.normalTexture)
		elif state == DISPLAY_SELECTED:
			self.displayNode.setTexture(self.lighterTexture)
		elif state == DISPLAY_HOVERED:
			self.displayNode.setTexture(self.lighterTexture)
		elif state == DISPLAY_PRESSED:
			self.displayNode.setTexture(self.normalTexture)
		elif state == DISPLAY_DISABLED:
			self.displayNode.setTexture(self.grayscaleTexture)
		self.displayState = state

	def getDisplayState(self):
		return self.displayState

class WhateverResources:
	def __init__(self):
		self.font = None
		self.font = loader.loadFont("tahoma.ttf")
		#self.font = loader.loadFont("/c/windows/fonts/tahoma.ttf") # absolute paths do not work here, why?

		self.buttonNormalTexture = loader.loadTexture("button-normal.png")
		self.buttonSelectedTexture = loader.loadTexture("button-selected.png")
		self.buttonHoveredTexture = loader.loadTexture("button-hovered.png")
		self.buttonPressedTexture = loader.loadTexture("button-pressed.png")
		self.buttonDisabledTexture = loader.loadTexture("button-disabled.png")

		self.lineeditNormalTexture = loader.loadTexture("lineedit-normal.png")
		self.lineeditSelectedTexture = loader.loadTexture("lineedit-selected.png")
		self.lineeditHoveredTexture = loader.loadTexture("lineedit-hovered.png")
		self.lineeditPressedTexture = loader.loadTexture("lineedit-pressed.png")
		self.lineeditDisabledTexture = loader.loadTexture("lineedit-disabled.png")

		self.buttonClickSound = loader.loadSfx("button-click.wav")

		self.checkNormalTexture = loader.loadTexture("checkbox-normal.png")
		self.checkSelectedTexture = loader.loadTexture("checkbox-selected.png")
		self.checkHoveredTexture = loader.loadTexture("checkbox-hovered.png")
		self.checkPressedTexture = loader.loadTexture("checkbox-pressed.png")
		self.checkDisabledTexture = loader.loadTexture("checkbox-disabled.png")

		self.checkCheckedTexture = loader.loadTexture("checkbox-checked.png")
		self.checkUncheckedTexture = loader.loadTexture("checkbox-unchecked.png")

		self.sliderNormalTexture = loader.loadTexture("slider-normal.png")
		self.sliderSelectedTexture = loader.loadTexture("slider-selected.png")
		self.sliderHoveredTexture = loader.loadTexture("slider-hovered.png")
		self.sliderPressedTexture = loader.loadTexture("slider-pressed.png")
		self.sliderDisabledTexture = loader.loadTexture("slider-disabled.png")

		self.sliderSliderTexture = loader.loadTexture("slider-slider.png")

class WhateverMingleMangle:
	def __init__(self):
		self.widgets = []
		self.selectionIndex = 0
		self.pressed = False

	def mouseEvent(self, x, y, pressed):
		for index in range(len(self.widgets)):
			widget = self.widgets[index]
			state = widget.getDisplayState()
			if state != DISPLAY_DISABLED:
				if widget.isPointInside(x, y):
					if (not self.pressed) and (pressed):
						widget.mousePressEvent()
						self.selectionIndex = index
					elif (not pressed) and (self.pressed) and (self.selectionIndex == index):
						widget.mouseReleaseEvent()

					if pressed and (self.selectionIndex == index):
						widget.setDisplayState(DISPLAY_PRESSED)
					else:
						widget.setDisplayState(DISPLAY_HOVERED)
				else:
					if index == self.selectionIndex:
						widget.setDisplayState(DISPLAY_SELECTED)
					else:
						widget.setDisplayState(DISPLAY_NORMAL)

		self.widgets[self.selectionIndex].mouseMoveEvent(x, y, pressed)
		self.pressed = pressed

	def keyPressEvent(self, key):
		if key == "tab":
			self.nextSelection()
		else:
			self.widgets[self.selectionIndex].keyPressEvent(key)

	def keyReleaseEvent(self, key):
		self.widgets[self.selectionIndex].keyReleaseEvent(key)

	def nextSelection(self):
		for i in range(1, len(self.widgets)):
			index = (self.selectionIndex + i) % len(self.widgets)
			if self.widgets[index].getDisplayState() != DISPLAY_DISABLED:
				self.selectionIndex = index
				self.mouseEvent(0, 0, False) # update widgets, not the nicest idea
				break

	def addWidget(self, widget):
		self.widgets += [ widget ]
